@using Microsoft.JSInterop
@using Radzen.Blazor
@using Radzen.Blazor.Rendering

@inherits RadzenComponent
@if (Visible)
{
<CascadingValue Value="@this">
@ChildContent
</CascadingValue>
<div @ref="Element" @attributes="@Attributes" class="@GetCssClass()" style="@Style" id="@GetId()">
@if (Width.HasValue && Height.HasValue)
{
    <CascadingValue Value="@this">
        <Legend />
        <svg style="width: 100%; height: 100%">
            <g transform="@($"translate({MarginLeft.ToInvariantString()}, {MarginTop.ToInvariantString()})")">
                <ClipPath />
                @if(ShouldRenderAxes())
                {
                    <ValueAxis />
                    <CategoryAxis />
                }
                @foreach (var series in Series.OrderBy(s => s.RenderingOrder))
                {
                    @if (series.Visible)
                    {
                        @series.Render(CategoryScale, ValueScale);
                    }
                }
            </g>
        </svg>
        @foreach (var series in Series.OrderBy(s => s.RenderingOrder))
        {
            @if (series.Visible && series is IChartDonutSeries donut)
            {
                @donut.RenderTitle(MarginLeft, MarginTop)
            }
        }
    @if (tooltip != null)
    {
        @tooltip
    }
    </CascadingValue>
}
</div>
}
@code {
    [Parameter]
    public ColorScheme ColorScheme { get; set; }

    double? Width { get; set; }

    double? Height { get; set; }

    double MarginTop { get; set; }

    double MarginLeft { get; set; }

    double MarginRight { get; set; }

    double MarginBottom { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    internal ScaleBase CategoryScale { get; set; } = new LinearScale();
    internal ScaleBase ValueScale { get; set; } = new LinearScale();
    internal IList<IChartSeries> Series { get; set; } = new List<IChartSeries>();
    internal RadzenColumnOptions ColumnOptions { get; set; } = new RadzenColumnOptions();
    internal RadzenBarOptions BarOptions { get; set; } = new RadzenBarOptions();

    internal RadzenLegend Legend { get; set; } = new RadzenLegend();
    internal RadzenCategoryAxis CategoryAxis { get; set; } = new RadzenCategoryAxis();
    internal RadzenValueAxis ValueAxis { get; set; } = new RadzenValueAxis();
    internal RadzenChartTooltipOptions Tooltip { get; set; } = new RadzenChartTooltipOptions();

    internal void AddSeries(IChartSeries series)
    {
        if (!Series.Contains(series))
        {
            Series.Add(series);
        }
    }

    internal void RemoveSeries(IChartSeries series)
    {
        Series.Remove(series);
    }

    private bool ShouldRenderAxes()
    {
        var pieType = typeof(RadzenPieSeries<>);
        var donutType = typeof(RadzenDonutSeries<>);

        return !Series.All(series =>
        {
            var type = series.GetType().GetGenericTypeDefinition();

            return type == pieType || type == donutType;
        });
    }

    internal bool ShouldInvertAxes()
    {
        return Series.Count > 0 && Series.All(series => series is IChartBarSeries);
    }

    private bool UpdateScales()
    {
        var valueScale = ValueScale;
        var categoryScale = CategoryScale;

        CategoryScale = new LinearScale { Output = CategoryScale.Output };
        ValueScale = new LinearScale { Output = ValueScale.Output };

        var visibleSeries = Series.Where(series => series.Visible).ToList();

        if (!visibleSeries.Any() && invisibleSeries.Any())
        {
            visibleSeries.Add(invisibleSeries.Last());
        }

        foreach (var series in visibleSeries)
        {
            CategoryScale = series.TransformCategoryScale(CategoryScale);
            ValueScale = series.TransformValueScale(ValueScale);
        }

        AxisBase xAxis = CategoryAxis;
        AxisBase yAxis = ValueAxis;

        if (ShouldInvertAxes())
        {
            xAxis = ValueAxis;
            yAxis = CategoryAxis;
        }
        else
        {
            CategoryScale.Padding = CategoryAxis.Padding;
        }

        CategoryScale.Resize(xAxis.Min, xAxis.Max);

        if (xAxis.Step != null)
        {
            CategoryScale.Step = xAxis.Step;
            CategoryScale.Round = false;
        }

        ValueScale.Resize(yAxis.Min, yAxis.Max);

        if (yAxis.Step != null)
        {
            ValueScale.Step = yAxis.Step;
            ValueScale.Round = false;
        }

        var legendSize = Legend.Measure(this);
        var valueAxisSize = ValueAxis.Measure(this);
        var categoryAxisSize = CategoryAxis.Measure(this);

        if (!ShouldRenderAxes())
        {
            valueAxisSize = categoryAxisSize = 0;
        }

        MarginTop = MarginRight = 32;
        MarginLeft = valueAxisSize;
        MarginBottom = Math.Max(32, categoryAxisSize);

        if (Legend.Visible)
        {
            if (Legend.Position == LegendPosition.Right || Legend.Position == LegendPosition.Left)
            {
                if (Legend.Position == LegendPosition.Right)
                {
                    MarginRight = legendSize + 16;
                }
                else
                {
                    MarginLeft = legendSize + 16 + valueAxisSize;
                }
            }
            else if (Legend.Position == LegendPosition.Top || Legend.Position == LegendPosition.Bottom)
            {
                if (Legend.Position == LegendPosition.Top)
                {
                    MarginTop = legendSize + 16;
                }
                else
                {
                    MarginBottom = legendSize + 16 + categoryAxisSize;
                }
            }
        }

        CategoryScale.Output = new ScaleRange { Start = MarginLeft, End = Width.Value - MarginRight };
        ValueScale.Output = new ScaleRange { Start = Height.Value - MarginBottom, End = MarginTop };

        ValueScale.Fit(ValueAxis.TickDistance);
        CategoryScale.Fit(CategoryAxis.TickDistance);

        var stateHasChanged = false;

        if (!ValueScale.IsEqualTo(valueScale))
        {
            stateHasChanged = true;
        }

        if (!CategoryScale.IsEqualTo(categoryScale))
        {
            stateHasChanged = true;
        }

        return stateHasChanged;
    }

    [JSInvokable]
    public void Resize(double width, double height)
    {
        var stateHasChanged = false;

        if (width != Width)
        {
            Width = width;
            stateHasChanged = true;

            CategoryScale.Output = new ScaleRange { Start = MarginLeft, End = Width.Value - MarginRight };
        }

        if (height != Height)
        {
            Height = height;
            stateHasChanged = true;

            ValueScale.Output = new ScaleRange { Start = Height.Value - MarginBottom, End = MarginTop };
        }

        if (stateHasChanged)
        {
            Refresh();
        }
    }

    RenderFragment tooltip;
    object tooltipData;
    double mouseX;
    double mouseY;

    [JSInvokable]
    public void MouseMove(double x, double y)
    {
        mouseX = x;
        mouseY = y;

        DisplayTooltip();
    }

    internal void DisplayTooltip()
    {
        if (Tooltip.Visible)
        {
            foreach (var series in Series)
            {
                if (series.Visible && series.Contains(mouseX - MarginLeft, mouseY - MarginTop))
                {
                    var data = series.DataAt(mouseX - MarginLeft, mouseY - MarginTop);

                    if (data != tooltipData)
                    {
                        tooltipData = data;
                        tooltip = series.RenderTooltip(data, MarginLeft, MarginTop);
                        StateHasChanged();
                    }

                    return;
                }
            }

            if (tooltip != null)
            {
                tooltipData = null;
                tooltip = null;

                StateHasChanged();
            }
        }
    }

    private IList<IChartSeries> invisibleSeries = new List<IChartSeries>();

    internal void ToggleSeries(IChartSeries series)
    {
        series.Visible = !series.Visible;

        if (series.Visible)
        {
            invisibleSeries.Remove(series);
        }
        else
        {
            invisibleSeries.Add(series);
        }

        Refresh();
    }

    private bool widthAndHeightAreSet = false;
    private bool firstRender = true;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);

        this.firstRender = firstRender;

        if (firstRender || visibleChanged)
        {
            visibleChanged = false;

            if (Visible)
            {
                var rect = await JSRuntime.InvokeAsync<Rect>("Radzen.createChart", Element, Reference);

                if (!widthAndHeightAreSet)
                {
                    widthAndHeightAreSet = true;

                    Resize(rect.Width, rect.Height);
                }
            }
        }
    }

    internal string ClipPath { get; set; }

    protected override void OnInitialized()
    {
        base.OnInitialized();

        ClipPath = $"clipPath{UniqueID}";
        CategoryAxis.Chart = this;
        ValueAxis.Chart = this;

        Initialize();
    }

    private void Initialize()
    {
        double width = 0;
        double height = 0;

        if (CurrentStyle.ContainsKey("height"))
        {
            var pixelHeight = CurrentStyle["height"];

            if (pixelHeight.EndsWith("px"))
            {
                height = Convert.ToDouble(pixelHeight.TrimEnd("px".ToCharArray()));
            }
        }

        if (CurrentStyle.ContainsKey("width"))
        {
            var pixelWidth = CurrentStyle["width"];

            if (pixelWidth.EndsWith("px"))
            {
                width = Convert.ToDouble(pixelWidth.TrimEnd("px".ToCharArray()));
            }
        }

        if (width > 0 && height > 0)
        {
            widthAndHeightAreSet = true;

            Width = width;
            Height = height;

            CategoryScale.Output = new ScaleRange { Start = MarginLeft, End = Width.Value - MarginRight };
            ValueScale.Output = new ScaleRange { Start = Height.Value - MarginBottom, End = MarginTop };
        }
    }

    private bool visibleChanged = false;

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        bool shouldRefresh = parameters.DidParameterChange(nameof(Style), Style);

        visibleChanged = parameters.DidParameterChange(nameof(Visible), Visible);

        await base.SetParametersAsync(parameters);

        if (shouldRefresh)
        {
            Initialize();
        }

        if (visibleChanged && !firstRender)
        {
            if (Visible == false)
            {
                await JSRuntime.InvokeVoidAsync("Radzen.destroyChart", Element);
            }
        }
    }

    internal void Refresh(bool force = true)
    {
        if (widthAndHeightAreSet)
        {
            var stateHasChanged = UpdateScales();

            if (stateHasChanged || force)
            {
                StateHasChanged();
            }
        }
    }

    public void Reload()
    {
        Refresh(true);
    }

    public override void Dispose()
    {
        base.Dispose();

        if (Visible && IsJSRuntimeAvailable)
        {
            JSRuntime.InvokeVoidAsync("Radzen.destroyChart", Element);
        }
    }

    protected override string GetComponentCssClass()
    {
        return $"rz-chart rz-scheme-{ColorScheme.ToString().ToLower()}";
    }
}
